package org.example.oop.io_stream;

import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
 * org.example.oop.stream
 *
 * @author moLei
 * @description 输入流，汉字在UTF-8编码规则中使用三个字节表示一个汉字，规则如下。UTF-8编码使用的是1-4个字节表示
 *      1110xxxx 10xxxxxx 10xxxxxx
 *      示例：汉---->转为utf-8为27721，二进制为01101100 01001001
 *          通过utf-8进行编码：11100110 10110001 10001001
 *      通过不同的编码规则进行解码和编码时就会导致乱码
 * @since 2025/6/18
 */
@Slf4j
public class ByteStreamDemo01 {

    private static String fileName = "C://idea//study//test.txt";

    private static String copyFileName = "C://idea//study//copy.txt";

    /**
     * 字节流，以字节为单元，可以操作任何数据
     * {@link java.io.InputStream} 和 {@link java.io.OutputStream}作为基类，字节输入流和字节输出流都继承自它们
     */

    /**
     * 文件字节输入流
     */
    public static void fileInputStreamDemo01() {
        // 获取需要输入的文件
        String fileName = "C://idea//study//test.txt";
        File file = new File(fileName);
        // 1. 创建字节输入流对象
        // try () {}这种方式为try-with-resources，jdk1.7之后支持，自动关闭try块中声明的资源，不需要显式的在finally中关闭资源
        // 文件不存在会报错
        try (InputStream inputStream = new FileInputStream(file)) {
            // 设置缓存大小，每次读取操作都会读取缓存大小字节的数据
            byte[] buffer = new byte[1024];
            // 读取数据的长度，读取成功返回>0，读取失败返回-1
            int len;
            // 循环读取数据，直到读取失败跳出循环，不适用byte数组时，直接使用read（）方法表示一次读取一个字节数据
            while ((len = inputStream.read(buffer)) != -1) {
                // 输出数据，将字节中的数据转换成字符串输出
                log.info(new String(buffer, 0, len));
            }
        } catch (Exception e) {
            log.error("文件操作报错：" + e.getMessage(), e);
        }
    }

    /**
     * 直接输出数据，覆盖原文件
     */
    public static void fileOutputStream() {

        File file = new File(fileName);
        // 文件不存在，会创建一个文件，前提是父级路径存在
        // 如果文件已存在，则会清空覆盖
        try (OutputStream outputStream = new FileOutputStream(file)) {
            // 创建字节数组，指定UTF-8进行编码
            byte[] bytes = "你好".getBytes(StandardCharsets.UTF_8);
            log.info(Arrays.toString(bytes));
            // 输出字节数组
            outputStream.write(bytes);
        } catch (Exception e) {
            log.error("文件操作报错：" + e.getMessage(), e);
        }
    }

    /**
     * 追加数据
     */
    public static void fileOutputStreamAppend() {
        // append参数，默认为false，表示覆盖，true表示追加
        try (FileOutputStream fos = new FileOutputStream(fileName, true)) {
            byte[] bytes = "hello world".getBytes();
            fos.write("\r\n".getBytes());
            fos.write(bytes);
        } catch (Exception e) {
            log.error("文件操作报错：" + e.getMessage(), e);
        }
    }

    /**
     * 读取指定数据
     */
    public static void fileInputStreamDemo02() {
        try (FileInputStream fis = new FileInputStream(fileName)) {
            // 创建字节数组，用于存储读取的数据
            byte[] bytes = new byte[1024];
            // 读取数据长度
            int len;
            // 跳过指定长度进行读取
            Long skipLen = fis.skip(3);
            log.info("跳过{}个字节数", skipLen);
            // 读取数据，放入bytes数组中，放入数组时将其从索引3开始，长度为bytes.length-3，此时bytes数组的可存储长度不为bytes.length，而是bytes.length-3
            // 每次读取时上一次读取的数据都会在bytes数组中，若后面读取的长度没有前面的长，则只会覆盖后续的长度，超出的部分数据还是前次的数据
            // 示例：文件中的数据为“hello world”，此时读取到的bytes中数据为【0，0，0，h，e，l，l，o，w，o，r，l，d, 0........0】
            len = fis.read(bytes, 3, bytes.length - 3);
            if (len != -1) {
                // 输出数据，需要截取有效长度，不然会出现重复打印的情况，根据上面代码可知，offset需要设置为3，不然后面的数据就会少3个字节
                log.info("读取数据{}个，{}", len, new String(bytes, 3, bytes.length - 3, StandardCharsets.UTF_8));
            } else {
                log.info("读取文件失败：{}", len);
            }
        } catch (Exception e) {
            log.error("文件操作报错：" + e.getMessage(), e);
        }
    }


    public static void fileCopy() {
        File srcFile = new File(fileName);
        File destFile = new File(copyFileName);
        // 使用Files.copy()方法，jdk封装的文件复制方法
        // Files.copy(srcFile.toPath(), destFile.toPath());
        try (FileInputStream fis = new FileInputStream(srcFile);
            FileOutputStream fos = new FileOutputStream(destFile)) {
            byte[] bytes = new byte[1024];

            int len;

            while ((len = fis.read(bytes)) != -1) {
                fos.write(bytes, 0, len);
            }
        } catch (Exception e) {
            log.error("文件操作报错：" + e.getMessage(), e);
        }
    }

    /**
     * 对字节流进行包装，添加了缓冲功能，自带缓冲区8192，提升读取效率
     */
    public static void BufferedInputStreamDemo() {
        // 可手动设置缓冲区大小，默认为8192
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName), 1024);
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(copyFileName), 1024)) {
            int len;
            // bis会从硬盘中一次性读取缓冲区大小的数据放入缓冲区中，bis.read()方法则每次会从缓冲区中读取一个字节数据
            // bos会将数据写入它的缓冲区，直到缓冲区满了写入文件，或手动flush()
            while ((len = bis.read()) != -1) {
                bos.write(len);
            }

            // 缓冲区的读取机制和上面的一样，但是bis.read(bytes)方法每次会从缓冲区中读取bytes长度的数据进行处理，所以 bis.read(bytes)方法的速度会比上面快一些
            // bos.write(bytes)方法每次会写入len长度的数据进入缓冲区
            byte[] bytes = new byte[1024];
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
            }
        } catch (Exception e) {
            log.error("文件操作报错：" + e.getMessage(), e);
        }

    }
    public static void main(String[] args) {
       fileOutputStream();
       // fileInputStreamDemo01();
       // fileOutputStreamAppend();
       fileInputStreamDemo02();
       //  fileCopy();

    }


}
